home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1992…ugust: Hack to the Future / ADC Developer CD (1992-08) (''Hack To The Future'')_iso / Dev.CD 199208.iso / Periodicals / develop / develop 9 code / EchoBox / 2BufRecordToBufCmd.c next >
Encoding:
C/C++ Source or Header  |  1992-01-06  |  16.3 KB  |  524 lines  |  [TEXT/MPS ]

  1. /*______________________________________________________*/
  2. /*                      Sound I/O Demo                    */
  3. /*                          by                          */
  4. /*                  RICHARD P. COLLYER                  */
  5. /*              Developer Technical Support             */
  6. /*                 Apple Computer, Inc.                 */
  7. /*                       11/14/91                       */
  8. /*______________________________________________________*/
  9.  
  10. /*
  11.  
  12. Notes:
  13.     •    There is a hardware limitation which keeps this code from
  14.         working on Macintosh computers which do not support stereo sound,
  15.         the Mac LC is an example of a machine which cn not run this code.
  16.         It isn't possible to record and playback at the same time unless
  17.         the machine can support stereo.
  18.  
  19.     •    Be careful writing interrupt routines in C.  If you use a global
  20.         array, the MPW C compilier may optimize the array's address into
  21.         a register before you can restore the applition's A5.  This will
  22.         incorrectly calculate the address of the array.  Using a "wrapper"
  23.         for setting A5 before calling the interrupt routine will prevent
  24.         this problem.
  25.  
  26.     •    When using the continuous recording feature of the Apple built-in
  27.         sound input device, there is a bug to be aware of.  If the recording
  28.         buffer is not a multiple of the device's internal buffer then you
  29.         will collect garbage and not audio data.  This means we need to be
  30.         careful to create a buffer of the right size.  This is also good
  31.         because the input device will be able to more effeciently record
  32.         data into our buffer.  So this is a doubly good idea.
  33.  
  34. */
  35.  
  36. /**********************************/
  37. /* #includes                      */
  38. /**********************************/
  39.  
  40. #include    <2BufRecordToBufCmd.h>
  41.  
  42. /**********************************/
  43. /* prototypes for the functions      */
  44. /**********************************/
  45.  
  46. void            TestTheSystem        (void);
  47. void            GetSoundDeviceInfo    (void);
  48. void            SetUpSounds            (Handle *bufferHandle, short *headerSize);
  49. int                FindHeaderSize        (void);
  50. void            BuildRecordStruct    (Handle bufferHandle);
  51. void            PlayBuffer            (Handle bufferHandle);
  52. void             ResetSoundHeader     (Handle bufferHandle);
  53. void            ExitWithMessage        (char *message, OSErr error);
  54. void            TimeToQuit            (void);
  55. pascal    void    MyRecComp            (SPBPtr inParamPtr);
  56. void             RealCompletion         (void);
  57.  
  58. /**********************************/
  59. /* Application Globals              */
  60. /**********************************/
  61.  
  62. SPBPtr                gRecordStruct;
  63. SndChannelPtr        gChannel;
  64. Handle                gBufferHandle[kNumberOfBuffers];
  65. Fixed                gSampleRate;
  66. long                gInternalBuffer;
  67. long                gSoundRefNum = 0;
  68. unsigned long        gSampleAreaSize;        // size of the sample area in the snd handle
  69. int                    gHeaderSize;            // Size of the Header to be skipped in for the bufferCmd
  70. short                gDataStart;                // Size of the Entire 'snd ' header
  71. short                gSampleSize;
  72. short                gNumberOfChannels;
  73. short                gWhichRecordBuffer = 0;
  74. OSErr                gError;
  75. OSType                gCompression;
  76.  
  77. /**********************************/
  78. /* TestTheSystem                  */
  79. /**********************************/
  80.  
  81. void TestTheSystem (void)
  82.  
  83. /* use Gestalt to make sure the app will work on the system*/
  84.  
  85. {
  86.     long            feature;
  87.     
  88.     gError = Gestalt(gestaltSoundAttr, &feature);
  89.     if (!gError) {
  90.         /* First Check to see that Sound Input is available */
  91.         if ( !(feature & (1 << gestaltHasSoundInputDevice)) )
  92.             ExitWithMessage ("No Sound Input Device, so The app can't run", 0);
  93.             
  94.         /* Second Check to see that the hardware supports stereo, if not then I can't
  95.         record and play sounds at the same time */
  96.         if ( !(feature & (1 << gestaltStereoCapability)) )
  97.             ExitWithMessage ("No Stereo support, so The app can't run", 0);
  98.         }
  99.     else
  100.         ExitWithMessage ("Gestalt failed in TestTheSystem", gError);
  101.         
  102.     return;
  103. }
  104.  
  105. /**********************************/
  106. /* GetSoundDeviceInfo              */
  107. /**********************************/
  108.  
  109. void GetSoundDeviceInfo (void)
  110.  
  111. /* Extract the information about the Sound Input Device to build the Sound Header */
  112.  
  113. {
  114.     long        value;
  115.     
  116.     /* Get the sample rate information for the snd header */
  117.         
  118.     gError = SPBGetDeviceInfo (gSoundRefNum,siSampleRate, (Ptr) &gSampleRate);
  119.     if (gError != noErr)
  120.         ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siSampleRate", gError);
  121.         
  122.     /* Get the sample size information for the snd header */
  123.         
  124.     gError = SPBGetDeviceInfo (gSoundRefNum,siSampleSize, (Ptr) &gSampleSize);
  125.     if (gError != noErr)
  126.         ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siSampleSize", gError);
  127.         
  128.     /* Get the compression type information for the snd header */
  129.         
  130.     gError = SPBGetDeviceInfo (gSoundRefNum,siCompressionType, (Ptr) &gCompression);
  131.     if (gError != noErr)
  132.         ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siCompressionType", gError);
  133.         
  134.     /* Get the number of input channels for the snd header */
  135.         
  136.     gError = SPBGetDeviceInfo (gSoundRefNum,siNumberChannels, (Ptr) &gNumberOfChannels);
  137.     if (gError != noErr)
  138.         ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siNumberChannels", gError);
  139.         
  140.     /* Get the size of the internal sound buffer for the snd buffer */
  141.         
  142.     gError = SPBGetDeviceInfo (gSoundRefNum,siDeviceBufferInfo, (Ptr) &gInternalBuffer);
  143.     if (gError != noErr)
  144.         ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siDeviceBufferInfo", gError);
  145.         
  146.     value = kMilliSecondsOfSound;
  147.     gError = SPBMillisecondsToBytes(gSoundRefNum, &value);
  148.     if (gError != noErr)
  149.         ExitWithMessage ("\pSPBMillisecondsToBytes failed in GetSoundDeviceInfo", gError);
  150.  
  151.     /* Round the buffer size to a multiple of the internal buffer size */
  152.     gSampleAreaSize = (value / gInternalBuffer) * gInternalBuffer;
  153.  
  154.     return;
  155. }
  156.  
  157. /**********************************/
  158. /* SetUpSounds                      */
  159. /**********************************/
  160.  
  161. void SetUpSounds (Handle *bufferHandle, short *headerSize)
  162.  
  163. /* SetUpSounds is a routine which allocates a snd buffer with the proper header
  164. and sample size, passing the handle and the real header size back to the caller. */
  165.  
  166. {
  167.     GetSoundDeviceInfo();
  168.     
  169.     /* Allocate largest Handle we could need */
  170.     *bufferHandle = NewHandle(gSampleAreaSize + kEstimatedHeaderSize);
  171.     gError = MemError();
  172.     if (gError != noErr || *bufferHandle == nil)
  173.         ExitWithMessage ("NewHandle failed in SetUpSounds", gError);
  174.     
  175.     /* Set up the header. After this call, we'll know how big the header size is */
  176.     gError = SetupSndHeader (*bufferHandle, gNumberOfChannels, gSampleRate, gSampleSize, gCompression, 
  177.                             kMiddleC, 0, headerSize);
  178.     if (gError != noErr)
  179.         ExitWithMessage ("SetupSndHeader failed in SetUpSounds", gError);
  180.     
  181.     /* Size the handle down to the size we really need */    
  182.     SetHandleSize(*bufferHandle, (Size) *headerSize + gSampleAreaSize);
  183.     gError = MemError();
  184.     if (gError != noErr)
  185.         ExitWithMessage ("SetHandleSize failed in SetUpSounds", gError);
  186.     
  187.     /* Move the handle high and lock it  */
  188.     MoveHHi (*bufferHandle);
  189.     gError = MemError();
  190.     if (gError != noErr)
  191.         ExitWithMessage ("MoveHHi failed in SetUpSounds", gError);
  192.  
  193.     HLock (*bufferHandle);
  194.     gError = MemError();
  195.     if (gError != noErr)
  196.         ExitWithMessage ("HLock failed in SetUpSounds", gError);
  197.  
  198.     return;
  199. }
  200.  
  201. /**********************************/
  202. /* FindHeaderSize                  */
  203. /**********************************/
  204.  
  205. int    FindHeaderSize (void)
  206.  
  207. /* This routine returns the number of bytes of the buffer we need to skip
  208. when calling bufferCmd.  The first several bytes of the sound header need to be skipped 
  209. so that the bufferCmd will be pointing at a SoundHeader Record and not an 'snd ' resource 
  210. header. The equations which are used in this routine are from Inside Macintosh VI page 20-22 */
  211.  
  212. {
  213.     int        headerSize;
  214.     short    highByte, lowByte;
  215.     short    format, numberOfSynths, numberOfCommands;
  216.     
  217.     highByte = **gBufferHandle[0];
  218.     lowByte = *(*gBufferHandle[0] + 1);
  219.     format = (highByte << 8) + lowByte;
  220.     
  221.     switch (format) {
  222.         case 1:        /* Format 1 snd */
  223.             headerSize = kBaseHeaderSize;
  224.             
  225.             // find the number of Synths in the snd header
  226.             highByte = *(*gBufferHandle[0] + 2);
  227.             lowByte = *(*gBufferHandle[0] + 3);
  228.             numberOfSynths = (highByte << 8) + lowByte;
  229.             headerSize += numberOfSynths * kSynthSize;
  230.             
  231.             // find the number of commands in the 'snd ' header
  232.             highByte = *(*gBufferHandle[0] + headerSize - 2);
  233.             lowByte = *(*gBufferHandle[0] + headerSize - 1);
  234.             numberOfCommands = (highByte << 8) + lowByte;
  235.             headerSize += numberOfCommands * kCmdSize;
  236.             break;
  237.  
  238.         case 2:        /* Format 2 snd */
  239.             headerSize = kBaseHeaderSize;
  240.             
  241.             // find the number of commands in the 'snd ' header
  242.             highByte = *(*gBufferHandle[0] + 4);
  243.             lowByte = *(*gBufferHandle[0] + 5);
  244.             numberOfCommands = (highByte << 8) + lowByte;
  245.             headerSize += numberOfCommands * kCmdSize;
  246.             break;
  247.  
  248.         default:
  249.             break;
  250.         }
  251.  
  252.     return (headerSize);
  253. }
  254.  
  255. /**********************************/
  256. /* BuildRecordStruct              */
  257. /**********************************/
  258.  
  259. void BuildRecordStruct (Handle bufferHandle)
  260.  
  261. /* build the gRecordStruct pointer and fill in the fields */
  262.  
  263. {
  264.     gRecordStruct = (SPBPtr) NewPtr(sizeof (SPB));
  265.     if (gRecordStruct == nil)
  266.         ExitWithMessage ("NewPtr failed in BuildRecordStruct", gError);
  267.  
  268.     gRecordStruct->inRefNum = gSoundRefNum;
  269.     gRecordStruct->count =  gSampleAreaSize;
  270.     gRecordStruct->milliseconds = 0;
  271.     gRecordStruct->bufferLength = gSampleAreaSize;
  272.     gRecordStruct->bufferPtr = (Ptr) ((*bufferHandle) + gDataStart);
  273.     gRecordStruct->completionRoutine = (ProcPtr) MyRecComp;
  274.     gRecordStruct->interruptRoutine = nil;
  275.     gRecordStruct->userLong = SetCurrentA5();
  276.     gRecordStruct->error = 0;
  277.     gRecordStruct->unused1 = 0;
  278.     
  279.     return;
  280. }
  281.  
  282. /**********************************/
  283. /* PlayBuffer                      */
  284. /**********************************/
  285.  
  286. void PlayBuffer (Handle bufferHandle)
  287.  
  288. /*This routine takes an 'snd ' buffer and a sound channel and turns the information into a 
  289. bufferCmd to the channel. */
  290.  
  291. {
  292.     SndCommand        localSndCmd;
  293.     
  294.     localSndCmd.cmd = bufferCmd;
  295.     localSndCmd.param1 = 0;
  296.     localSndCmd.param2 = (long) ((*bufferHandle) + gHeaderSize);
  297.     
  298.     gError = SndDoCommand (gChannel, &localSndCmd, false);
  299.     if (gError != noErr)
  300.         DebugStr("\pSndDoCommand failed in PlayBuffer (type 'g' return)");
  301.         
  302.     return;
  303. }
  304.  
  305. /**********************************/
  306. /* ExitWithMessage                  */
  307. /**********************************/
  308.  
  309. void ExitWithMessage (char *message, OSErr error)
  310.  
  311. /* All errors are passed to this routine to display a message and the error result.
  312. it also exits the application, because this sample demos the sound manager, not 
  313. error handleing.  */
  314.  
  315. {
  316.     GrafPtr                    savePort;
  317.     DialogPtr                myDialog;
  318.     short                    itemtype, itemHit;
  319.     Handle                    itemHand;
  320.     Rect                    itemRect;
  321.     char                    *errStrPtr, errStr[256];
  322.  
  323.     errStrPtr = &errStr;
  324.  
  325.     GetPort(&savePort);
  326.     myDialog = GetNewDialog(kErrorDialogID, nil, (WindowPtr) -1);
  327.     SetPort(myDialog);
  328.     
  329.     numtostring(error,errStrPtr);
  330.     GetDItem(myDialog,kErrNumStatText,&itemtype,&itemHand,&itemRect);
  331.     setitext(itemHand, errStrPtr);
  332.  
  333.     GetDItem(myDialog,kMsgStatText,&itemtype,&itemHand,&itemRect);
  334.     setitext(itemHand, message);
  335.  
  336.     do {
  337.         ModalDialog(nil,&itemHit);
  338.         } while (itemHit != kOKButton);
  339.             
  340.     DisposDialog(myDialog);
  341.     SetPort(savePort);
  342.     
  343.     TimeToQuit ();
  344. }
  345.  
  346.  
  347. /**********************************/
  348. /* TimeToQuit                      */
  349. /**********************************/
  350.  
  351. void TimeToQuit (void)
  352.  
  353. /* Once I am out of the loop it is time to clean up - stop the currently playing sound,
  354. Dispose of the Channel, close the input driver, and dispose of the Buffer handles and
  355. gRecordStruct Ptr. */
  356.  
  357. {
  358.     short                index, recordingStat, meterlevel;
  359.     unsigned long        totalSamples, numberOfSamples, totalMSec, numberOfMSec;
  360.     
  361.     // check each global to make sure they were allocated before disposing of them
  362.     
  363.     if (gSoundRefNum != 0) {
  364.         gError = SPBGetRecordingStatus (gSoundRefNum, &recordingStat, &meterlevel,
  365.                                 &totalSamples, &numberOfSamples, &totalMSec, &numberOfMSec);
  366.         if (gError != noErr)
  367.             DebugStr("\SPBGetRecordingStatus failed in TimeToQuit (type 'g' return)");
  368.             
  369.         if (recordingStat > 0) {
  370.             // make sure that recording has stopped before I close the sound driver
  371.             gError = SPBStopRecording (gSoundRefNum);
  372.             if (gError != noErr)
  373.                 DebugStr("\pSPBStopRecording failed in TimeToQuit (type 'g' return)");
  374.             }
  375.             
  376.         gError = SPBCloseDevice (gSoundRefNum);
  377.         if (gError != noErr)
  378.             DebugStr("\pSPBCloseDevice failed in TimeToQuit (type 'g' return)");
  379.         }
  380.         
  381.     if (gBufferHandle[0] != nil)
  382.         for (index = 0; index < kNumberOfBuffers; ++index)
  383.             DisposeHandle (gBufferHandle[index]);
  384.  
  385.     if (gRecordStruct != nil)
  386.         DisposePtr ((Ptr) gRecordStruct);
  387.  
  388.     if (gChannel != nil) {
  389.         gError = SndDisposeChannel (gChannel,true);
  390.         if (gError != noErr)
  391.             DebugStr("\pSndDisposeChannel failed in TimeToQuit (type 'g' return)");
  392.         }
  393.     
  394.     ExitToShell();
  395. }
  396.  
  397. /**********************************/
  398. /* main                              */
  399. /**********************************/
  400.  
  401. main()
  402. {
  403.     short                index, contOnOff = 1;
  404.     
  405.     InitGraf(&qd.thePort);
  406.     FlushEvents(everyEvent, 0);
  407.     InitWindows();
  408.     InitDialogs(nil);
  409.  
  410.     TestTheSystem ();    // test the system to make sure the app will work
  411.  
  412.     /* Open sound input drive (whichever one is selected in the sound cdev) */
  413.     
  414.     gError = SPBOpenDevice (kDefaultDriver, siWritePermission, &gSoundRefNum);
  415.     if (gError != noErr)
  416.         ExitWithMessage ("SPBOpenDevice failed in main", gError);
  417.         
  418.     /* turn on continuous recording */
  419.         
  420.     gError = SPBSetDeviceInfo (gSoundRefNum,siContinuous, (Ptr) &contOnOff);
  421.     if (gError != noErr)
  422.         ExitWithMessage ("SPBSetDeviceInfo failed in main", gError);
  423.         
  424.     /* build the kNumberOfBuffers snd Buffers */
  425.         
  426.     for (index = 0; index < kNumberOfBuffers; ++index)
  427.         SetUpSounds (&gBufferHandle[index], &gDataStart);
  428.     
  429.     /* determine the part of the header which needs to be skipped before calling bufferCmd */
  430.     
  431.     gHeaderSize = FindHeaderSize();
  432.     
  433.     /* build the gRecordStruct pointer and fill in the fields */
  434.         
  435.     BuildRecordStruct (gBufferHandle[gWhichRecordBuffer]);
  436.  
  437.     /* open the sound channel which I will need to play from */
  438.  
  439.     gChannel = nil;
  440.     // initNoInterp gets rid of the clicks between the buffers
  441.     gError = SndNewChannel (&gChannel, sampledSynth, initNoInterp, nil);
  442.     if (gError != noErr)
  443.         ExitWithMessage ("SndNewChannel failed in main", gError);
  444.             
  445.     gError = SPBRecord (gRecordStruct, true); // start recording
  446.     if (gError != noErr)
  447.         ExitWithMessage ("SPBRecord failed in main", gError);
  448.  
  449.     while (!Button() || (gRecordStruct->error < noErr));    /* main loop of the app */
  450.         
  451.     // quitting time
  452.         
  453.     if (gRecordStruct->error < noErr)
  454.         ExitWithMessage ("\pAn error occurred while recording", gRecordStruct->error);
  455.     else
  456.         TimeToQuit ();
  457. }
  458.  
  459. /**********************************/
  460. /* MyRecComp                      */
  461. /**********************************/
  462.  
  463. pascal void MyRecComp (SPBPtr inParamPtr)
  464.  
  465. // This is the Completion Routine which is called every time the recording Buffer,
  466. // is full.  This routine needs to setup A5 to be the application's A5 which was being
  467. // saved in the userLong of the parameter block.
  468.  
  469. // Due to the MPW C compiler optimization scheme, access to global arrays will be pointed
  470. // to in an address register as an offset of A5.  This will happen before we have a chance
  471. // to set A5 to our application's A5.  To avoid this we need to first restore our A5 and
  472. // then call the completion routine.
  473.  
  474. {
  475.     long                storeA5;
  476.     
  477.     /* Set A5 so the completion routine has access to the Application Globals */
  478.     
  479.      storeA5 = SetA5 (inParamPtr->userLong);
  480.     
  481.     RealCompletion ();
  482.     
  483.     storeA5 = SetA5 (storeA5);
  484.  
  485.     return;
  486. }
  487.  
  488. /**********************************/
  489. /* RealCompletion                  */
  490. /**********************************/
  491.  
  492. void RealCompletion (void)
  493.  
  494. // Setup the current snd handle to have the correct size of the sample data we've just
  495. // finished recording.  The size of the sample data is in the count field of the
  496. // recording parameter block.  Then play this new buffer of data.  Finally, we will
  497. // start another recording after switching to the other buffer.
  498.  
  499. {
  500.     OSErr            err;
  501.     SoundHeaderPtr    header;
  502.  
  503.     // if there has been an error then do nothing
  504.     if (gRecordStruct->error < 0) return;
  505.     
  506.     // get pointer to SoundHeader and update length value in the header
  507.     header = (SoundHeaderPtr) (*(gBufferHandle[gWhichRecordBuffer]) + gHeaderSize);
  508.     header->length = gRecordStruct->count;
  509.     
  510.     PlayBuffer (gBufferHandle[gWhichRecordBuffer]);        // Play the buffer
  511.     
  512.     gWhichRecordBuffer = NextBuffer (gWhichRecordBuffer);    // move on to next buffer
  513.     
  514.      // update gRecordStruct
  515.      gRecordStruct->bufferPtr = (*(gBufferHandle[gWhichRecordBuffer]) + gDataStart);
  516.     gRecordStruct->milliseconds = 0;
  517.     gRecordStruct->count = gSampleAreaSize;
  518.     gRecordStruct->bufferLength = gSampleAreaSize;
  519.     
  520.     err = SPBRecord (gRecordStruct, true);        // queue up another record
  521.     if (err) 
  522.         DebugStr ("\pSPBRecord died in RealCompletion");
  523. }
  524.